package org.jeecg.config.shiro.filters;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.MD5Util;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.mybatis.TenantContext;
import org.jeecg.config.shiro.JwtToken;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Description: 鉴权登录拦截器
 * @Author: Scott
 * @Date: 2018/10/7
 **/
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {

    private boolean allowOrigin = true;

    private final static String[] agent = { "Android", "iPhone", "iPod","iPad", "Windows Phone", "MQQBrowser" };

    private String requestcookie;

    public JwtFilter(){}
    public JwtFilter(boolean allowOrigin, String requestcookie){
        this.allowOrigin = allowOrigin;
        this.requestcookie = requestcookie;
    }

    private void sendAuthError(ServletResponse response , String msg){
        try {
            JSONObject jo = new JSONObject();
            jo.put("status",HttpServletResponse.SC_UNAUTHORIZED);
            jo.put("error","Internal Server Error");
            jo.put("message",msg);
//            jo.put("timestamp","");
//            jo.put("path","");

            HttpServletResponse httpResponse = WebUtils.toHttp(response);
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.getWriter().write(jo.toJSONString());
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }catch (Exception ex){
            log.error("执行response.getWriter()方法异常");
        }
    }

//    private void sendError(ServletResponse response , String msg){
//        try {
//            JSONObject jo = new JSONObject();
//            jo.put("status",HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
//            jo.put("error","Internal Server Error");
//            jo.put("message",msg);
////            jo.put("timestamp","");
////            jo.put("path","");
//
//            HttpServletResponse httpResponse = WebUtils.toHttp(response);
//            httpResponse.setContentType("application/json;charset=utf-8");
//            httpResponse.getWriter().write(jo.toJSONString());
//            httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
//        }catch (Exception ex){
//            log.error("执行response.getWriter()方法异常");
//        }
//    }


    private void sendFeignAuthError(ServletResponse response , String msg){
        try {
            JSONObject jo = new JSONObject();
            jo.put("status",HttpServletResponse.SC_UNAUTHORIZED);
            jo.put("error","用户凭证已过期");
            jo.put("message",msg);

            HttpServletResponse httpResponse = WebUtils.toHttp(response);
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.getWriter().write(jo.toJSONString());
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }catch (Exception ex){
            log.error("执行response.getWriter()方法异常");
        }
    }

    /**
     * 执行登录认证
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        String ip = oConvertUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest());
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;

        //内部feign请求不鉴权
        String url = httpServletRequest.getRequestURI();
        if (StrUtil.isNotEmpty(url) && url.contains("/feignopenapi/")) {
            log.info("feign请求，ip地址:  {} ,请求地址:  {}", ip, url);
            return true;
        }

        String token = httpServletRequest.getHeader(CommonConstant.X_ACCESS_TOKEN);
        if (token == null) {
            log.error("Token不能为空，IP地址:  " + ip);
            sendAuthError(response, "Token不能为空");
            //todo 记录攻击ip次数，根据规则加入黑名单后续处理
            return false;
        }

        String username = StrUtil.EMPTY;
        try {
            username = JwtUtil.getUsername(token);
            if (username == null) {
                //todo 记录攻击ip次数，根据规则加入黑名单后续处理
                log.error("token非法无效，IP地址:  " + ip);
                sendAuthError(response, "Token不能为空");
                return false;
            }
        }catch (Exception e){
            log.error("token非法无效，IP地址:  " + ip + "," + e.getMessage());
            e.printStackTrace();
            sendAuthError(response, "token非法无效");
            return false;
        }
        //todo 根据黑名单ip，判断ip是否允许登陆
//        if(isInIpBlackList(ip)){
//            log.error("ip在黑名单中，禁止访问，IP地址:  " + ip);
//            sendError(response, "禁止访问");
//            return false;
//        }

        try {
            return executeLogin(request, response);
        } catch (Exception e) {

            log.warn("登录验证失败 {} {} {}", username, e.getMessage(), httpServletRequest.getRequestURI());

            String feignRequest = httpServletRequest.getHeader(CommonConstant.FEIGN_REQUEST);
            if (StrUtil.isNotEmpty(feignRequest)) {
                sendFeignAuthError(response, "Token失效，请重新登录");
                return false;
            }

            sendAuthError(response, "登录验证失败,请重新登录");
            return false;
        }

    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        return false;
    }

    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(CommonConstant.X_ACCESS_TOKEN);
        return token != null;
    }

    /**
     *
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(CommonConstant.X_ACCESS_TOKEN);
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入，如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功，返回true

        //如果requestcookie不等于1，表示只检测token不检测cookie
        if (!"1".equals(requestcookie)) {
            return true;
        }

        String ua = httpServletRequest.getHeader("User-Agent");
        //如果小程序没有带token，那就认证请求头
        Cookie[] cookies = httpServletRequest.getCookies();
        if (null == cookies) {
            //log.info("App: User-Agent is {} - {}", ua, requestcookie);
            for (String item : agent) {
                if (ua.contains(item)) {
                    //如果不存在cookie，尝试读头，没请求头没cookie不合法
                    String accessCookie = httpServletRequest.getHeader(CommonConstant.X_PC_COOKIE);
                    //log.info("CommonConstant.X_ACESS_COOKIE is {}", accessCookie);
                    if (StrUtil.isEmpty(accessCookie)) {
                        log.error("X_ACCESS_TOKEN 为空， {} {}", accessCookie, ua);
                        throw new AuthenticationException("X_ACCESS_TOKEN 已失效，请重新获取");
                    }

                    String userMD5 = MD5Util.MD5Encode(JwtUtil.getUsername(token), "utf-8");
                    if (accessCookie.equals(userMD5)) {
                        return true;
                    } else {
                        log.error("X_ACCESS_TOKEN 未匹配 {} {} {}", accessCookie, ua, userMD5);
                        throw new AuthenticationException("X_ACCESS_TOKEN 已失效，请重新获取");
                    }
                }
            }
        } else {
            //log.info("PC: User-Agent is {} - {}", ua, requestcookie);
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(CommonConstant.X_PC_COOKIE)) {
                    if (StrUtil.isEmpty(cookie.getValue())) {
                        log.error("Cookie X_ACESS_COOKIE 为空，不允许访问");
                        throw new AuthenticationException("X_ACCESS_TOKEN 已失效，请重新获取");
                    }

                    String userMD5 = MD5Util.MD5Encode(JwtUtil.getUsername(token), "utf-8");
                    //log.info("Cookie对比X_ACESS_COOKIE, {} -> {}", cookie.getValue(), userMD5);
                    if (cookie.getValue().equals(userMD5)) {
                        return true;
                    } else {
                        log.error("Cookie X_ACESS_COOKIE，不允许访问 {} {}",  cookie.getValue(), userMD5);
                        throw new AuthenticationException("X_ACCESS_TOKEN 已失效，请重新获取");
                    }
                }
            }
        }

        throw new AuthenticationException("X_ACCESS_TOKEN 未匹配");
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        if(allowOrigin){
            httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
            //update-begin-author:scott date:20200907 for:issues/I1TAAP 前后端分离，shiro过滤器配置引起的跨域问题
            // 是否允许发送Cookie，默认Cookie不包括在CORS请求之中。设为true时，表示服务器允许Cookie包含在请求中。
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
            //update-end-author:scott date:20200907 for:issues/I1TAAP 前后端分离，shiro过滤器配置引起的跨域问题
        }
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        //update-begin-author:taoyan date:20200708 for:多租户用到
        String tenant_id = httpServletRequest.getHeader(CommonConstant.TENANT_ID);
        TenantContext.setTenant(tenant_id);
        //update-end-author:taoyan date:20200708 for:多租户用到
        return super.preHandle(request, response);
    }
}
